package org.sl.flvplayer.controls {	
	import org.sl.flvplayer.controls.Controller;
	import org.sl.flvplayer.controls.JavaScriptController;
	import org.sl.flvplayer.data.Config;
	import org.sl.flvplayer.elements.SeekControllerElement;
	import org.sl.flvplayer.flyweights.FWPlayerCommand;
	import org.sl.flvplayer.helper.Stack;
	import org.sl.flvplayer.main.FLVPlayerApplication;
	import org.sl.flvplayer.view.PlayerScreen;

	import flash.display.StageAlign;
	import flash.display.StageDisplayState;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.FullScreenEvent;
	import flash.media.SoundTransform;
	import flash.ui.Mouse;

	/**
	 * Provides means to control the player and furthermore adds the PlayerScreen and the GUI to the stage.</br>
	 * The PlayerLogic class executes all commands and specifies what methods each command exactly triggers. 
	 * 
	 * @package org.sl.flvplayer.controls
	 * @author Dan
	 * @version
	 * @see org.sl.flvplayer.data.Config
	 * @see org.sl.flvplayer.data.Playlist
	 * @see org.sl.flvplayer.controls.Controller
	 * @see org.sl.flvplayer.controls.JavaScriptController
	 * @see org.sl.flvplayer.view.PlayerScreen
	 * @see org.sl.flvplayer.elements.SeekControllerElement
	 * @see org.sl.flvplayer.elements.VolumeControllerElement
	 * 
	 * @see org.sl.flvplayer.helper.Stack
	 */ 
	public class PlayerLogic {
		private static var instance : PlayerLogic;

		public var playerScreen : PlayerScreen;
		
		private var commandStack : Stack = new Stack();
		private var controllerGUI : Controller;
		private var storedVolume : Number ;
		private var soundTransform : SoundTransform = new SoundTransform();

		/**
		 * Margin between the playerscreen and the bottom of the stage.
		 */
		public  var margin : Number;

		
		/**
		 * Ensures that there is only one instance of the class by using the singleton pattern.
		 * 
		 * @return instance of the <code>PlayerLogic</code> class
		 */
		public static function getInstance() : PlayerLogic {
			if(!instance) instance = new PlayerLogic();	
			return instance;
		}

		/**
		 * PlayerLogic constructor initializes the Playerscreen and the GUI.
		 */
		function PlayerLogic() {		
			addPlayerScreen();
			addControllerGUI();
		}

		/**
		 * Adds the playerScreen to the stage and handles further stage issues. 
		 */
		private function addPlayerScreen() : void {
			playerScreen = PlayerScreen.getInstance();
			FLVPlayerApplication.getInstance().addChild(playerScreen);			
			playerScreen.stage.align = StageAlign.TOP_LEFT;
			playerScreen.stage.scaleMode = StageScaleMode.NO_SCALE;
			playerScreen.stage.addEventListener(Event.RESIZE, resizeHandler);
			playerScreen.stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullscreenHandler);
		}

		/**
		 * Adds the controllerGUI to the stage and checks whether the GUI should be shown or not.  
		 */
		private function addControllerGUI() : void {
			controllerGUI = Controller.getInstance();
			FLVPlayerApplication.getInstance().addChild(controllerGUI);
			controllerGUI.render();	
			if(Config.getInstance().showControls && playerScreen.stage.stageWidth > 160) { 
				margin = controllerGUI.btnSeek.height;
			} else {
				margin = 0;
				controllerGUI.removeControls();
			}
		}		

		/**
		 * Handles which skinn is shown on the fullscreen button and resets the highlightcolor when leaving fullscreenmode.
		 * 
		 * @param p_event
		 */
		private function fullscreenHandler(p_event : FullScreenEvent) : void {
			if(p_event.fullScreen) {
				controllerGUI.changeStatus("fullscreenOff", true);
			} else {
				controllerGUI.changeStatus("fullscreen", true);
				controllerGUI.resetColor();
				// when the playerapplication leaves fullscreenmode the mousepointer might be hidden, this ensures the mousepointer is properly shown
				Mouse.show();
				controllerGUI.btnScreenPlay.visible = true;
			}
		}		

		/**
		 * Specifies what methods are triggered after the player was resized. 
		 * 
		 * @param p_event
		 */
		private function resizeHandler(p_event : Event) : void {
			scaleFunction();
			playerScreen.resize();
			controllerGUI.updatePos();
		}

		/**
		 * Scales the video and constrain its proportions.
		 */	
		public function scaleFunction() : void {
			playerScreen.playerDisplay.width = playerScreen.stage.stageWidth;
			playerScreen.playerDisplay.height = playerScreen.stage.stageHeight - margin;
			playerScreen.playerDisplay.scaleX < playerScreen.playerDisplay.scaleY ? playerScreen.playerDisplay.scaleY = playerScreen.playerDisplay.scaleX : playerScreen.playerDisplay.scaleX = playerScreen.playerDisplay.scaleY;					
			playerScreen.playerDisplay.x = (playerScreen.stage.stageWidth / 2) - (playerScreen.playerDisplay.width / 2);
			playerScreen.playerDisplay.y = (playerScreen.stage.stageHeight / 2) - (playerScreen.playerDisplay.height / 2) - (margin / 2);
			if (Config.getInstance().scalePreviewImageToVideoSize) PlayerScreen.getInstance().playerDisplay.resizePreviewimage();
		}

		/**
		 * Initializes the loader in the seekbar. 
		 */
		private function initLoader() : void {
			var sCE : SeekControllerElement = controllerGUI.btnSeek as SeekControllerElement; 
			sCE.initLoader();
		}	

		/**
		 * Checks whether the commandStack is empty and either executes the command when the stack is empty or adds the command on top in the stack.
		 * 
		 * @param p_command command
		 */
		public function command(p_command : FWPlayerCommand) : void {
			if (commandStack.isEmpty()) {
				executeCommand(p_command);
			} else {
				commandStack.addItem(p_command);
			}
		}

		/**
		 * Executes commands and further functions necessary for player functionality.
		 * 
		 * @param p_command received command
		 */
		private function executeCommand(p_command : FWPlayerCommand) : void {
			// FLVPlayerApplication.SOS_TRACER.info("Execute Command: " + p_command.command);
			switch (p_command.command) {
				case FWPlayerCommand.SOURCE:	
					// to prevent overlapping of sources the source and the state of the player is reset
					playerScreen.reset();
					playerScreen.source = p_command.params[0];
					// externalInterface for Javacript control
					JavaScriptController.getInstance().initExternalInterface();
					JavaScriptController.getInstance().sendGoogleAnalyticsURL(playerScreen.source.videoSource);
					// the loaderbar of the seekbar is initialized
					initLoader();
					break;
				case FWPlayerCommand.RENDER:
					playerScreen.render();
					// by default the volume is set to 100
					controllerGUI.updateVolume(100);
					controllerGUI.changeStatus("sound", true);
					// after 
					scaleFunction();		
					break;
				case FWPlayerCommand.PLAY:
					playerScreen.source.playStream(); //playerScreen.source.ns.resume();
					playerScreen.source.isStarted = true;
					playerScreen.source.isPlaying = true;
					controllerGUI.changeStatus("pause", true);
					controllerGUI.changeStatus("screen_pause", true);
					playerScreen.playerDisplay.removePreviewImage();
					nextCommand();
					break;
				case FWPlayerCommand.STOP:
					// to set the plabacktime to 0 in php pseudostreaming the videoSource  
					if(Config.getInstance().streamingMode) {
						playerScreen.source.ns.play(playerScreen.source.videoSource);
					}	
					playerScreen.source.ns.seek(0);
					playerScreen.source.ns.pause();
					playerScreen.source.isPlaying = false;
					controllerGUI.changeStatus("play", true);
					controllerGUI.changeStatus("screen_play", true);		
					nextCommand();
					break;
				case FWPlayerCommand.PAUSE:
					if (!playerScreen.source.isStarted) {
						playerScreen.source.playStream(); //playerScreen.source.ns.resume();
						playerScreen.source.isStarted = true;
						playerScreen.source.isPlaying = true;
						controllerGUI.changeStatus("pause", true);
						controllerGUI.changeStatus("screen_pause", true);
						// seekbutton is only dragable when the video starts to play 
						controllerGUI.initSeek();
						// when playback has startet the previewimage is removed
						playerScreen.playerDisplay.removePreviewImage();									
					} else {
						playerScreen.source.ns.togglePause();
						playerScreen.source.isPlaying = !playerScreen.source.isPlaying;
						var type : String = (playerScreen.source.isPlaying) ? "pause" : "play";
						var typeScreen : String = (playerScreen.source.isPlaying) ? "screen_pause" : "screen_play";
						controllerGUI.changeStatus(type, true);
						controllerGUI.changeStatus(typeScreen, true);
					}
					nextCommand();
					break;				
				case FWPlayerCommand.FULLSCREEN:
					if(playerScreen.stage.displayState == StageDisplayState.NORMAL) {
						playerScreen.stage.displayState = StageDisplayState.FULL_SCREEN;
						playerScreen.playerScreenInteraction.addMousePointerAction();
					} else {
						playerScreen.stage.displayState = StageDisplayState.NORMAL;
						playerScreen.playerScreenInteraction.removeMousePointerAction();
					}
					nextCommand();
					break;
				case FWPlayerCommand.VOLUME:
					if(p_command.params != null) {
						volume = p_command.params[0];
					}
					nextCommand();
					break;
				case FWPlayerCommand.MUTE:
					if(playerScreen.source.sound.volume > 0) {					
						storeVolume();
						mute();
					} else {
						unMute();					
					}
					nextCommand();
					break;
				case FWPlayerCommand.SEEK:
					if(Config.getInstance().streamingMode) {
						// searches for the next adequate keyframe for the parsed seektime 
						for (var i : Number = 0;i < playerScreen.source.keyFrameTimes.length; i++) {
							var j : Number = i + 1;
							if((playerScreen.source.keyFrameTimes[i] <= p_command.params[0]) && (playerScreen.source.keyFrameTimes[j] >= p_command.params[0])) {							
								// when a found instead of seeking with seek(seektime), play is executed along with the videoSource and the keyframeposition  
								// to prevent playing while the video is paused and seek is used after ns.play(...) ns.seek(seektime) is executed,
								// this causes the seekbutton to jump to the desired seekposition i nthe new streamed videopart!
								if(!playerScreen.source.isPlaying) {
									playerScreen.source.ns.play(playerScreen.source.videoSource + "&start=" + playerScreen.source.keyFrameFilePositions[i]);
									playerScreen.source.ns.seek(p_command.params[0]);
									playerScreen.source.ns.pause();
								}else playerScreen.source.ns.play(playerScreen.source.videoSource + "&start=" + playerScreen.source.keyFrameFilePositions[i]);
							}		
						}
					}else playerScreen.source.ns.seek(p_command.params[0]);		
					nextCommand();
					break;
				case FWPlayerCommand.SHOWCONTROLS:	
					controllerGUI.render();
					nextCommand();
					break;
				case FWPlayerCommand.HIDECONTROLS:	
					controllerGUI.removeControls();
					nextCommand();
					break;			
				default:
					// FLVPlayerApplication.SOS_TRACER.fatal("Command not exists: " + p_command.command);
					break;
			}
		}

		/**
		 * Gets the next command.
		 */
		private function nextCommand() : void {
			if (!commandStack.isEmpty()) {
				executeCommand(commandStack.getNext());
			}
		}

		/**
		 * Sets the volume and checks whether the volume is within range.
		 * 
		 * @param volumePercentage Volume calculatet in percents.
		 */
		private function set volume(volumePercentage : Number) : void {	
			// the ranges a set due to inaccuracy of the parsed volume
			if(volumePercentage > 3) {
				controllerGUI.changeStatus("sound", true);
			}			
			if (volumePercentage > 90) {
				volumePercentage = 100;
			}
			else if (volumePercentage <= 10) {
				mute();
			}
			soundTransform.volume = volumePercentage / 100;
			playerScreen.source.sound = soundTransform;
		}

		/**
		 * 
		 */
		private function get volume() : Number {		
			return playerScreen.source.sound.volume * 100 ;
		}

		/**
		 * Stores the current volume. 
		 */		
		private function storeVolume() : void {
			storedVolume = playerScreen.source.sound.volume;		
		}	

		/**
		 * Mutes the sound and sets the proper skin of the mute button.
		 */
		private function mute() : void {
			soundTransform.volume = 0;
			playerScreen.source.sound = soundTransform;
			controllerGUI.changeStatus("mute", true);
			controllerGUI.updateVolume(0);
		}

		/**
		 * Unmutes the sound and sets the sound skin of the mute button.
		 */
		private function unMute() : void {
			soundTransform.volume = storedVolume;
			playerScreen.source.sound = soundTransform;
			controllerGUI.changeStatus("sound", true);
			controllerGUI.updateVolume(volume);
		}	
	}
}
